<?php defined( 'BASEPATH' ) or die( 'Restricted' );

/*
This code is Copyright (C) by TMLA INC.  ALL RIGHTS RESERVED.
Please view license.txt in /tgsf_core/legal/license.txt or
http://tgWebSolutions.com/opensource/tgsf/license.txt
for complete licensing information.
*/

define( 'ACH_DIRECT_LIVE_SERVER', '5050' );
define( 'ACH_DIRECT_TEST_SERVER', '6050' );
define( 'ACH_EOL', "\n" );

define( 'ACH_EFT_SALE',   '20' );
define( 'ACH_EFT_CREDIT', '23' );
define( 'ACH_EFT_VOID',   '24' );
define( 'ACH_EFT_VERIFY', '26' );

define( 'ACH_ACCOUNT_TYPE_CHECKING',  'C' );
define( 'ACH_ACCOUNT_TYPE_SAVINGS',   'S' );

define( 'ACH_RESPONSE_DENIED',   'D' );
define( 'ACH_RESPONSE_APPROVED', 'A' );
define( 'ACH_RESPONSE_ERROR',    'E' );
/**
 * This is the interface to ACHDirect and paymentsgateway
 *
 *  This is a Full Interface and not SWP!
 *
 *  Originally Written, 9/20/2009, 11:38 PM
 *  By: R.Bloom
 *
 *
 * Specific Field Information
 *
 * Header Fields *
 *
 * These fields identify the merchant and transaction type.
 * There are nine additional merchant data fields that will be echoed in the response message.
 *
 * pg_merchant_id                merchant's six digit ID code
 * pg_password                   merchant's processing password
 * pg_transaction_type           indicates transaction type (see Table 10)
 * pg_merchant_data_[1-9]        nine fields returned with response fields
 *
 * Customer/Order Information Fields
 *
 * These fields contain the transaction details: order, amount and customer information.
 *
 * pg_total_amount                             amount to be charged/credited to customer
 * pg_sales_tax_amount                         sales tax amount; required field for procurement card transactions, optional otherwise
 * pg_consumer_id                              assigned by merchant, returned with response
 * ecom_consumerorderid                        assigned by merchant, returned with response
 * ecom_walletid                               assigned by merchant, returned with response
 * pg_billto_postal_name_company               company name
 * ecom_billto_postal_name_first               customer's first name
 *
 * ecom_billto_postal_name_last                customer's last name
 * ecom_billto_postal_street_line1             customer's street address
 * ecom_billto_postal_street_line2             customer's street address (if necessary)
 * ecom_billto_postal_city                     customer's city
 * ecom_billto_postal_stateprov                customer's state (abbreviated)
 * ecom_billto_postal_postalcode               customer's ZIP code
 * ecom_billto_postal_countrycode              customer's country
 * ecom_billto_telecom_phone_number            customer's phone number
 * ecom_billto_online_email                    customer's email address
 *
 * pg_billto_ssn                               customer's social security number
 * pg_billto_dl_number                         customer's driver's license number
 * pg_billto_dl_state                          customer's driver's license state of issue
 * pg_billto_date_of_birth                     customer's date of birth (MM/DD/YYYY)
 * pg_entered_by                               name or ID of the person entering the data; appears in the Virtual Terminal transaction display window
 *
 * (PC)    required for Procurement Card transactions, optional otherwise
 * (AVS)   required for AVS checks specified in the pg_avs_method field, optional otherwise
 *
 * --------------------------------------------------------------------------------
 * pg_schedule_frequency                       specifies the frequency of the recurring transaction Value Frequency Period
 *
 *     10    weekly          every seven days
 *     15    biweekly      every fourteen days
 *     20    monthly        same day every month
 *     25    bi-monthly      every two months
 *     30    quarterly    every 3 months
 *     35    semiannually  twice a year
 *     40    yearly          once year
 *
 * --------------------------------------------------------------------------------
 * pg_transaction_type  Type Description Comments
 *
 *
 * Credit Card
 *     10      SALE			Customer is charged
 *     11      AUTH ONLY	Authorization only, CAPTURE transaction required
 *     12      CAPTURE		Completes AUTH ONLY transaction
 *     13      CREDIT		Customer is credited
 *     14      VOID			Cancels non-settled transactions
 *     15      PRE-AUTH		Customer charge approved from other source
 *
 * EFT
 *
 *     20      SALE			Customer is charged
 *     21      AUTH ONLY	Authorization only, CAPTURE transaction required
 *     22      CAPTURE		Completes AUTH ONLY transaction
 *     23      CREDIT		Customer is credited
 *     24      VOID			Cancels non-settled transactions
 *     25      FORCE		Customer charged (no validation checks)
 *     26      VERIFY ONLY	Verification only, no customer charge
 *
 * Recurrance
 *
 *     40      SUSPEND		Suspends a recurring transaction
 *     41      ACTIVATE		Reactivates a recurring transaction
 *     42      DELETE		Deletes a recurring transaction
 */

/**
 * Base Class for ACH Messages.  ACH Messages are basically name value pairs.
 * An Array of Strings and values is kept internally in _vars to indicate possible
 * values that can be sent in the itnerface.  This actually gives us three things.
 * A Variable, it's Name and it's value.  This method allows for easy addition of
 * new variables without much coding (really any coding)
 * and allows for retrieving the name of a variable without writing a mapping.
 *
 * outgoing messages (ACHTransaction) are formatted to name value pairs with a newline
 * after each pair.
 *
 * incoming messages (ACHResponse) are formatted as read from paymentsgeteway as name value pairs.
 * the trim kills the newline that PG sends after each name value pair
 *
 * The placement of 'endofdata' => null in the array of fields is simply symbolic
 * of it's place in an actual message and totally unnecessary for this interface
 *
 */

class ACHMessage extends tgsfBase
{
	protected $_vars;

	//------------------------------------------------------------------------
	public function __construct()
	{
	   $this->_vars = array( 'endofdata' => null );
	}
	//--------------------------------------------------------------------------
	/**
	 * Reset all values to null to clear a message
	 */
	public function clear()
	{
		$this->_vars = array_fill_keys( array_keys( $this->_vars ), null );
	}
	//------------------------------------------------------------------------
	/**
	 * Echo values for any variables that are NOT null.
	 */
	public function debug()
	{
		foreach ( $this->_vars as $var => $value )
		{
			if ( $value !== null )
			{
				echo str_pad( $var, 50 ) . ' = ' . $value . PHP_EOL;
			}
		}
	}
	//------------------------------------------------------------------------
	public function __get( $name )
	{
		if ( array_key_exists( $name, $this->_vars ) )
		{
			return $this->_vars[$name];
		}

		throw new tgsfException( '(When Getting) Unknown Value Named: ' . $name . ' in ACHMessage.' );
	}
	//------------------------------------------------------------------------
	/**
	*
	*/
	public function __set( $name, $value )
	{
		if ( array_key_exists( $name, $this->_vars ) )
		{
			$this->_vars[$name] = $value;
		}
		else
		{
			throw new tgsfException( '(When Setting) Unknown Value Named: ' . $name . ' in ACHMessage' );
		}
	}
}

// -----------------------------------------------------------------------------
// Outbound Transactions
// -----------------------------------------------------------------------------

class ACHTransaction extends ACHMessage
{
	public function __construct()
	{
		parent::__construct();

	   $this->_vars = array(

		// Header
			'pg_merchant_id'                    => null,
			'pg_password'                       => null,
			'pg_total_amount'                   => null,
			'pg_transaction_type'               => null,
			'pg_merchant_data_1'                => null,
			'pg_merchant_data_2'                => null,
			'pg_merchant_data_3'                => null,
			'pg_merchant_data_4'                => null,
			'pg_merchant_data_5'                => null,
			'pg_merchant_data_6'                => null,
			'pg_merchant_data_7'                => null,
			'pg_merchant_data_8'                => null,
			'pg_merchant_data_9'                => null,

		// Customer / Order Information
			'pg_sales_tax_amount'               => null,
			'pg_consumer_id'                    => null,
			'ecom_consumerorderid'              => null,
			'ecom_walletid'                     => null,
			'pg_billto_postal_name_company'     => null,
			'ecom_payment_check_account_type'   => null,
			'ecom_payment_check_account'        => null,
			'ecom_payment_check_trn'            => null,
			'ecom_billto_postal_name_first'     => null,
			'ecom_billto_postal_name_last'      => null,
			'ecom_billto_postal_street_line1'   => null,
			'ecom_billto_postal_street_line2'   => null,
			'ecom_billto_postal_city'           => null,
			'ecom_billto_postal_stateprov'      => null,
			'ecom_billto_postal_postalcode'     => null,
			'ecom_billto_postal_countrycode'    => null,
			'ecom_billto_telecom_phone_number'  => null,
			'ecom_billto_online_email'          => null,
			'pg_billto_ssn'                     => null,
			'pg_billto_dl_number'               => null,
			'pg_billto_dl_state'                => null,
			'pg_billto_date_of_birth'           => null,
			'pg_entered_by'                     => null,
			'pg_original_trace_number'          => null,
			'pg_original_authorization_code'    => null,

		// Recurrance
			'pg_schedule_quantity'              => null,
			'pg_schedule_frequency'             => null,
			'pg_schedule_recurring_amount'      => null,
			'pg_schedule_start_date'            => null,

		// Misc
			'pg_customer_ip_address'            => null,
			'pg_software_name'                  => null,
			'pg_software_version'               => null,
			'pg_avs_method'                     => null,

		// End
			'endofdata'                         => null );
	}
	//------------------------------------------------------------------------
	/**
	* Formats a DSI message for ACHDirect.
	* DSI messages are name value pairs terminated by newlines
	* followed by endofdata and a final newline
	*
	* -------------------------
	* pg_terminal_id=123555
	* pg_password=some_password
	* endofdata
	* -------------------------
	*/
	public function getOutput()
	{
		$result = '';

		foreach ( $this->_vars as $name => $value )
		{
			if ( $value !== null )
			{
				$result .= $name . '=' . $value . ACH_EOL;
			}
		}

		$result .= 'endofdata' . ACH_EOL;

		return $result;
	}
}

/**
* ACH Responses - a class to parse the values returned in a DSI transaction
*/
class ACHResponse extends ACHMessage
{
	public $ach_tran = null;
	public function __construct()
	{
		parent::__construct();

		$this->_vars = array(
			'pg_merchant_id'                        => null,
			'pg_transaction_type'                   => null,
			'pg_merchant_data_1'                    => null,
			'pg_merchant_data_2'                    => null,
			'pg_merchant_data_3'                    => null,
			'pg_merchant_data_4'                    => null,
			'pg_merchant_data_5'                    => null,
			'pg_merchant_data_6'                    => null,
			'pg_merchant_data_7'                    => null,
			'pg_merchant_data_8'                    => null,
			'pg_merchant_data_9'                    => null,
			'pg_total_amount'                       => null,
			'pg_sales_tax_amount'                   => null,
			'pg_consumer_id'                        => null,
			'ecom_consumerorderid'                  => null,
			'ecom_walletid'                         => null,
			'ecom_billto_postal_name_first'         => null,
			'ecom_billto_postal_name_last'          => null,
			'pg_billto_postal_name_company'         => null,
			'ecom_billto_online_email'              => null,
			'pg_response_type'                      => null,
			'pg_response_code'                      => null,
			'pg_response_description'               => null,
			'pg_avs_result'                         => null,
			'pg_trace_number'                       => null,
			'pg_authorization_code'                 => null,
			'pg_preauth_result'                     => null,
			'pg_preauth_code'                       => null,
			'pg_preauth_description'                => null,
			'endofdata'                             => null );
	}
	//------------------------------------------------------------------------
	/**
	* Process a name value pair and store in an object variable
	* @param string A name/value pair separated by an "="
	*/
	public function process( $nvp )
	{
		$parts = explode( '=', $nvp );
		$name  = trim( $parts[0] );

		if ( count( $parts ) == 2 && array_key_exists( $name, $this->_vars ))
		{
			$value = trim( $parts[1] );
			$this->{$name} = $value;
		}
		else
		{
			throw new appException( 'Invalid Response Line: ' . $nvp );
		}
	}
	//------------------------------------------------------------------------
	function getErrorDetails()
	{
		if ( $this->pg_response_type == ACH_RESPONSE_APPROVED )
		{
			return '';
		}

		$errorDescription = $this->pg_response_description;
		$parts = explode( ":", $this->pg_response_description );
		$errorCode = $parts[0];
		if ( count( $parts ) > 1 )
		{
			$errorDescription = $parts[1];
		}

		//$result = "Code=$errorCode, Description=$errorDescription" . PHP_EOL;

		$errors = array(
			'F01' => 'Mandatory Field Missing',
			'F03' => 'Invalid Field Name',
			'F04' => 'Invalid Field Value',
			'F05' => 'Duplicate Field',
			'F07' => 'Conflicting Fields',

			'U01' => 'Merchant Authorization Revoked',
			'U02' => 'Customer Account is in ACH Direct \'s known bad list.',
			'U03' => 'Merchant has exceeded daily limit',
			'U04' => 'Merchant has exceeded monthly limit',
			'U05' => 'AVS State/Zip check failed',
			'U06' => 'AVS State/Area code check failed',
			'U07' => 'AVS anonymous email check failed',
			'U08' => 'Customer\'s account has exceeded daily txn QUANTITY (not dollar) limit',
			'U09' => 'Customer\'s account has more transactions than the merchant\'s velocity window allows for',
			'U10' => 'Duplicate Transaction',
			'U11' => 'Recurring Txn not found',
			'U12' => 'Original Txn not voidable or capturable',
			'U13' => 'Txn to be voided or captured was not found',
			'U14' => 'Void/Capture and original txn types do not agree - cc/eft',
			// missing 15-17 in PDF from ach direct's documentation
			'U18' => 'Void or capture failed',
			'U19' => 'Account ABA is invalid',
			'U20' => 'Invalid Credit Card Number',
			'U21' => 'Bad start date - date is malformed',
			// 22-50 missing
			'U51' => 'Merchant account is not live',
			'U52' => 'Merchant is not approved for the txn type - cc/eft',
			'U53' => 'Txn exceeds merchant\'s per txn limit',
			'U54' => 'Merchant\'s configuration requires updating - call customer support',
			// 55-79 missing
			'U80' => 'Transaction was declined due to preauthorization (ATM Verify) result',
			// 81-82 missing
			'U83' => 'Transaction was declined due to authorizer declination',
			'U84' => 'Timeout - Authorizer unreachable',
			'U85' => 'Error - Authorizer error',
			'U86' => 'Authorizer AVS check failed'
			);

		$errorName = 'Unknown Error';

		if ( array_key_exists( $errorCode, $errors ) )
		{
			$errorName = $errors[$errorCode];
		}

		switch ( $this->pg_response_type )
		{
		case ACH_RESPONSE_ERROR:
			$errorType = 'ACH ERROR';
			break;
		case ACH_RESPONSE_DENIED:
			$errorType = 'ACH DECLINED/DENIED';
			break;
		}

		return PHP_EOL .
				PHP_EOL . '    Type: ' . $errorType .
				PHP_EOL . '    Code:  ' . $errorCode .
				PHP_EOL . '    Error: ' . $errorName .
				PHP_EOL . '    Field: ' . $errorDescription . PHP_EOL;

		return $result;
	}
}

/**
* ACHDirect class - Main paymentsgateway.net Interface Class
*
*/
class ACHDirect extends tgsfBase
{
	public $host;
	public $port;

	/**
	* The constructor
	* @param What port to use.  Use defined values: ACH_DIRECT_TEST_SERVER and ACH_DIRECT_LIVE_SERVER
	*/
	public function __construct( $port = '' )
	{
		$this->host = config('pg_host');
		$this->port = $port;

		if ( empty( $port ) )
		{
			// Default to TEST Server for initial testing.
			$this->port = ACH_DIRECT_TEST_SERVER;
			//$this->port = ACH_DIRECT_LIVE_SERVER;
		}
	}
	//------------------------------------------------------------------------
	/**
	 * Create an ACHTransaction object that can be set and later executed.
	 *
	 */
	public function &createTransaction( $merchantID )
	{
		$tran = new ACHTransaction();

		if ( empty( $merchantID ) )
		{
			$tran->pg_merchant_id = config('pg_merchant_id');
		}
		else
		{
			$tran->pg_merchant_id = $merchantID;
		}

		$tran->pg_password    = config('pg_password');
		$tran->pg_entered_by  = config('pg_entered_by');

		return $tran;
	}
	//------------------------------------------------------------------------
	/**
	* Returns an ACHResponse object based on the DSI communication with Payments Gateway
	*/
	public function &executeTransaction( &$tran )
	{
		$err_no = null;
		$err_msg = null;

		try
		{
			// Open a connection to host:port
			$sock = fsockopen( $this->host, $this->port, $err_no, $err_msg, 10 );
		}
		catch ( Exception $e )
		{
			$retVal = false;
			return $retVal;
		}

		if ( ! $sock )
		{
			throw new appException( "ACHDirect Error - Could not open a socket connection to host ($host) on port ($port): " . $err_msg );
		}

		// send DSI message formatted by the ACHTransaction
		fputs( $sock, $tran->getOutput() );

		$achResponse = new ACHResponse();

		// read back one line at a time (ACH_EOL)
		while( $line = fgets( $sock ) )
		{
			if ( substr( $line, 0, 9 ) == 'endofdata' )
			{
				break;
			}

			// ACHMessage parses the line as a name value pair and stores the variable and value
			$achResponse->process( $line );
		}

		// All done, close the socket connection
		fclose( $sock );

		return $achResponse;
	}
}
